package cfca.seal.util;

import cfca.sadk.algorithm.common.Mechanism;
import cfca.sadk.algorithm.common.PKIException;
import cfca.sadk.algorithm.sm2.SM2PublicKey;
import cfca.sadk.lib.crypto.JCrypto;
import cfca.sadk.lib.crypto.Session;
import cfca.sadk.org.bouncycastle.asn1.ASN1Set;
import cfca.sadk.org.bouncycastle.asn1.DERBitString;
import cfca.sadk.org.bouncycastle.asn1.pkcs.CertificationRequest;
import cfca.sadk.org.bouncycastle.asn1.pkcs.CertificationRequestInfo;
import cfca.sadk.org.bouncycastle.asn1.x500.X500Name;
import cfca.sadk.org.bouncycastle.asn1.x509.AlgorithmIdentifier;
import cfca.sadk.org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
import cfca.sadk.org.bouncycastle.crypto.params.RSAKeyParameters;
import cfca.sadk.org.bouncycastle.crypto.util.PublicKeyFactory;
import cfca.sadk.org.bouncycastle.jcajce.provider.asymmetric.rsa.BCRSAPublicKey;
import cfca.sadk.util.Base64;
import cfca.sadk.util.KeyUtil;
import cfca.sadk.util.P10Request;
import java.security.KeyPair;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.util.Map;

public class P10Helper
{
  public Session openSession(String deviceType)
    throws SecurityException
  {
    try
    {
      JCrypto.getInstance().initialize(deviceType, null);
      return JCrypto.getInstance().openSession(deviceType);
    } catch (Exception e) {
      throw new SecurityException("initialize/open session failure", e);
    }
  }

  private final boolean isSM2(String certAlg)
  {
    return ("SM2".equals(certAlg)) || ("sm3WithSM2Encryption".equals(certAlg));
  }

  public KeyPair buildKeyPair(Session session, Mechanism mechanism, int kLength)
    throws SecurityException
  {
    try
    {
      return KeyUtil.generateKeyPair(mechanism, kLength, session);
    } catch (PKIException e) {
      throw new SecurityException("generate keypair failure", e);
    }
  }

  public byte[] buildP10(Session session, KeyPair keypair, String certAlg)
    throws SecurityException
  {
    if ((session == null) || (keypair == null)) {
      throw new SecurityException("null not allowed for session/keypair");
    }
    if (certAlg == null) {
      throw new SecurityException("null not allowed for session/keypair");
    }
    String keyAlg = keypair.getPublic().getAlgorithm();
    if ("SM2".equals(keyAlg)) {
      if (!certAlg.contains("SM2"))
        throw new SecurityException("certAlg not allowed for keypair");
    }
    else if ("RSA".equals(keyAlg)) {
      if (!certAlg.contains("RSA"))
        throw new SecurityException("certAlg not match with keypair");
    }
    else {
      throw new SecurityException("algorithm not allowed for keypair");
    }

    Mechanism signALg = new Mechanism(certAlg);

    P10Request p10 = new P10Request(session);
    PrivateKey privateKey = keypair.getPrivate();
    PublicKey publicKey = keypair.getPublic();
    ASN1Set attributes = null;
    X500Name subject = new X500Name("CN=certRequisition,O=CFCA TEST CA,C=CN");

    byte[] p10Base64Data = null;
    try {
      p10Base64Data = p10.generatePKCS10Request(signALg, subject, attributes, publicKey, privateKey);
    }
    catch (PKIException e) {
      throw new SecurityException("generate keypair failure", e);
    }
    return p10Base64Data;
  }

  public boolean verifyP10(Session session, byte[] p10Base64Bytes)
    throws SecurityException
  {
    try
    {
      CertificationRequest certificationRequest = CertificationRequest.getInstance(Base64.decode(p10Base64Bytes));
      Object signAlgOID = certificationRequest.getSignatureAlgorithm().getAlgorithm();
      String signAlg = (String)Mechanism.OIDALGMap.get(signAlgOID);
      CertificationRequestInfo PKCS10 = certificationRequest.getCertificationRequestInfo();
      PublicKey publicKey = null;
      if (isSM2(signAlg)) {
        publicKey = new SM2PublicKey(PKCS10.getSubjectPublicKeyInfo().getPublicKeyData().getBytes());
      } else {
        RSAKeyParameters param = (RSAKeyParameters)PublicKeyFactory.createKey(PKCS10.getSubjectPublicKeyInfo());
        publicKey = new BCRSAPublicKey(param);
      }

      byte[] signBytes = certificationRequest.getSignature().getBytes();

      byte[] dataBytes = PKCS10.getEncoded();

      return session.verify(new Mechanism(signAlg), publicKey, dataBytes, signBytes);
    }
    catch (Exception e)
    {
      throw new SecurityException("verifyReq failure", e);
    }
  }
}